using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Mbs.Collections;
using Mbs.ComponentModel.Container.Context;
using Mbs.Reflection;

namespace Mbs.ComponentModel.Container.Activation
{
    [Serializable]
    public class DefaultActivator : AbstractActivator
    {
        
        protected override object InternalCreate(ICreateContext context)
        {
            return CreateInstance(context);
        }

        internal object CreateInstance(ICreateContext context)
        {
            ConstructorInfo constructorInfo = null;
            var impType = context.Component.Implementation;

            if (impType.IsOpenGenericType())
                impType = impType.MakeCloseGenericType(context.GenericParameters);

            if (context.NamedArgs != null && context.NamedArgs.Count > 0)
                constructorInfo = FindEligibleConstructor(impType, context.NamedArgs);
            else if (context.Args != null && context.Args.Length > 0)
                constructorInfo = FindEligibleConstructor(impType, context.Args);
            else
                constructorInfo = FindEligibleConstructor(impType);

            if (constructorInfo == null)
            {
                if (context.Component.Implementation.IsValueType)
                    return null;
                throw new ArgumentException("No appropriate constructor");
            }

            try
            {
                object[] parms = null;

                if (context.NamedArgs != null && context.NamedArgs.Count > 0)
                    parms = context.NamedArgs.Values.ToArray();
                else if (context.Args != null && context.Args.Length > 0)
                    parms = context.Args;
                else
                    parms = GetParameters(context.Component.Implementation, constructorInfo);

                return constructorInfo.FastInvoke(parms);
            }
            catch (Exception ex)
            {
                if (ex.InnerException != null)
                    throw ex.InnerException;
                throw ex;
            }
        }

        private ConstructorInfo FindEligibleConstructor(Type impType, IDictionary<string, object> args)
        {
            var result = (from c in impType.GetConstructors()
                          let ps = c.GetParameters() 
                          where 
                            ps.Length == args.Count
                            && ps.TrueForAll(p => args.ContainsKey(p.Name) 
                            &&(
                              args[p.Name].GetType() == p.ParameterType
                              || p.ParameterType.IsAssignableFrom(args[p.Name].GetType())
                              ))
                          select c).FirstOrDefault();
            
            if (result == null)
                throw new ActivatorException("Could not find eligible constructor for " + impType.FullName);

            
            return result;
        }

        private ConstructorInfo FindEligibleConstructor(Type impType, object[] args)
        {
            var result = (from c in impType.GetConstructors()
                          let ps = c.GetParameters()
                          where ps.Length == args.Length
                          && ps.TrueForAll(
                            p=>args[p.Position].GetType() == p.ParameterType
                            || p.ParameterType.IsAssignableFrom(args[p.Position].GetType()))
                          select c).FirstOrDefault();
            if (result == null)
                throw new ActivatorException("Could not find eligible constructor for " + impType.FullName);
            return result;
        }

        private object[] GetParameters(Type type, ConstructorInfo constructor)
        {
            var ps = constructor.GetParameters();

            int length = ps.Length;
            object[] result = new object[length];
            foreach (var p in ps)
            {
                var att = p.GetAttribute<InjectAttribute>(false);
                if (att == null
                    || string.IsNullOrEmpty(att.Id)
                    || !Kernel.HasRegister(att.Id))
                    result[p.Position] = Kernel.Get(p.ParameterType);
                else
                    result[p.Position] = Kernel.Get(att.Id);
            }
            return result;
        }

        private ConstructorInfo FindEligibleConstructor(Type typeToConstruct)
        {
            var methods = (from c in typeToConstruct.GetConstructors()
                           let ps = c.GetParameters()
                           where ps.TrueForAll(p => Filter(p))
                           orderby ps.Length descending
                           select c).ToArray();

            var result = (from m in methods
                          let attr = m.GetAttribute<InjectAttribute>(true)
                          where attr != null
                          select m).FirstOrDefault();
            if (result != null)
                return result;

            var constructor = methods.FirstOrDefault();

            if (constructor == null)
                throw new ActivatorException("Could not find eligible constructor for " + typeToConstruct.FullName);
            return constructor;
        }

        private bool Filter(ParameterInfo p)
        {
            var att = p.GetAttribute<InjectAttribute>(false);
            if (att != null && !string.IsNullOrEmpty(att.Id))
                return Kernel.HasRegister(att.Id);

            return Kernel.HasRegister(p.ParameterType);
        }
    }
}
